package iagovsothello.ai;

import iagovsothello.core.*;
import iagovsothello.logger.*;

public abstract class Agent implements java.io.Serializable {

	private static final long serialVersionUID = 4030719283504198279L;
	private static final long MINIMUM_TIME = 1000;
	
	/**
	 * Numero del giocatore associato a questo agente: per Othello i numeri ammissibili sono 1 per il giocatore bianco e 2 per il giocatore nero.
	 */
	protected int playerNumber;
	
	/**
	 * Attributo opzionale: se è presente allora a ciascuna chiamata di <code>nextMove</code> sarà generata una nuova <code>Entry</code> contenente
	 * le informazioni sul calcolo effettuato; ciascuna classe che estende <code>Agent</code> deve esplicitamente verificare se <code>log != null</code>
	 * e in caso affermativo generare queste informazioni, all'interno del metodo <code>computeNextMove</code>.
	 */
	protected Log log;
	
	/**
	 * Se <code>true</code>, allora l'agente lavora in modalità debug; in questa modalità non è richiesta la diretta interazione 
	 * con un essere umano e non viene applicata la regola di garantire un tempo minimo di elaborazione pari a MINIMUM_TIME.
	 */
	private boolean debugMode = false;
	
	public Agent()
	{
		super();
		this.playerNumber = 1;
		this.log = null;
	}
	
	public Agent(int playerNumber)
	{
		super();
		this.playerNumber = playerNumber;
		this.log = null;
	}
	
	public Agent(int playerNumber, boolean debugMode) 
	{
		super();
		this.playerNumber = playerNumber;
		this.log = null;
		this.debugMode = debugMode;
	}
	
	public Agent(int playerNumber, boolean debugMode, Log log) 
	{
		super();
		this.playerNumber = playerNumber;
		this.log = log;
		this.debugMode = debugMode;
	}
	
	/**
	 * Metodo che resituisce il valore della funzione di utilitˆ di un dato stato del gioco. 
	 * Assegna 1000 se la partita � vinta, 100 se � un pareggio e 0 se � una sconfitta
	 * @param state
	 * @return
	 */
	public int utilityValue(GameState state)
	{
		int win = state.getVictor();
		int utility = 0;
		
		if(win == 0)
			utility = 50;
		else if(win == playerNumber)
			utility = 1000;
		else
			utility = 0;
		
		return utility;
		
	}
	
	public int getPlayerNumber() 
	{
		return playerNumber;
	}

	public void setPlayerNumber(int playerNumber) 
	{
		this.playerNumber = playerNumber;
	}

	public Log getLog() 
	{
		return log;
	}

	public void setLog(Log log) 
	{
		this.log = log;
	}

	public boolean isDebugMode() 
	{
		return debugMode;
	}

	public void setDebugMode(boolean debugMode) 
	{
		this.debugMode = debugMode;
	}

	public abstract String getName();
	
	public boolean equals(Agent a)
	{
		return this.toString().equals(a.toString());
	}
	
	public abstract Agent clone();
	
	/**
	 * Questo metodo deve essere richiamato da qualsiasi implementazione del metodo <code>clone()</code> di tutte le sottoclassi di <code>Agent</code>. Copia tutti gli attributi (o meglio, i loro riferimenti)
	 * pubblici, protetti e privati non statici di questa classe in un nuovo oggetto. 
	 * @param a oggetto, sottoclasse di <code>Agent</code>, in cui copiare gli attributi
	 */
	protected void copyAgentAttributes(Agent a)
	{
		a.playerNumber = this.playerNumber;
		a.log = this.log;
		a.debugMode = this.debugMode;
	}
	
	/**
	 * Metodo usato per calcolare la prossima mossa. Quando si usa questo metodo, gli si deve passare come parametro un'interfaccia; viene
	 * creato un thread che esegue fisicamente il calcolo e una volta fatto lo fornisce all'interfaccia stessa usando i suoi metodi <code>setNextMove</code> e 
	 * <code>nextMove()</code>.
	 * @param oif classe che estende l'interfaccia <code>OthelloInterface</code>, che mette a disposizione i metodi <code>setNextMove</code> e <code>nextMove()</code>
	 * @param currentState stato corrente del gioco sulla base del quale calcolare la mossa
	 */
	public void nextMove(OthelloInterface oif, GameState currentState)
	{
		AgentThread worker = new AgentThread(oif,currentState);
		worker.setDaemon(true);
		worker.start();
	}
	
	/**
	 * Metodo usato per calcolare la prossima mossa. A differenza dell'altro metodo omonimo, questo metodo non crea un nuovo thread per il calcolo della mossa,
	 * ma effettua il calcolo sullo stesso thread del chiamante, per poi restituire normalmente la mossa successiva.
	 * @param currentState stato corrente del gicoo sulla base del quale calcolare la mossa
	 * @return posizione in cui si intende inserire la nuova pedina
	 */
	public Position nextMove(GameState currentState)
	{
		return computeNextMove(currentState);
	}
	
	/**
	 * Metodo da implementare da qualsiasi agente, e che calcola la mossa da eseguire. Questo metodo non andrebbe chiamato direttamente, bensì
	 * andrebbe usato il metodo pubblico <code>nextMove</code>.
	 * @param currentState stato attuale del gioco
	 * @return posizione in cui l'agente ha scelto di inserire la pedina
	 */
	protected abstract Position computeNextMove(GameState currentState);
	
	public class AgentThread extends Thread {
		
		private OthelloInterface oif;
		private GameState currentState;
		
		public AgentThread(OthelloInterface oif, GameState currentState) 
		{
			super();
			this.oif = oif;
			this.currentState = currentState;
		}

		public void run()
		{
			//tempo di inizio della computazione
			long startingTime = System.currentTimeMillis();
			
			//computazione
			Position p = computeNextMove(currentState);
			
			//tempo di fine della computazione
			long endingTime = System.currentTimeMillis();
			
			/*se il tempo impiegato è minore del minimo, il thread si mette in attesa:
			 questo meccanismo rende il gioco più facile da seguire per un essere umano */
			if (!debugMode && endingTime-startingTime < MINIMUM_TIME)
			{
				try {
					//sleep(MINIMUM_TIME-(endingTime-startingTime));
					sleep(Math.max((endingTime-startingTime)*50,MINIMUM_TIME));
				} catch (InterruptedException e) { }
			}
			
			oif.setNextMove(p);
			oif.nextMove();
		}
	}
}
